;#############################
;## OBTENCION DATOS CLIENTE ##
;#############################
;# En este modulo obtenemos la informacion del cliente, cargando una
;# instancia si ya esta definida y sino le pedimos los datos al usuario.
(defmodule modulo-obtencion
    (import MAIN ?ALL)
    (export ?ALL)
)

;##############################################
;## FUNCIONES GENERICAS PARA HACER PREGUNTAS ##
;##############################################

;# HACE UNA PREGUNTA Y LA GUARDA EN UN STRING
(deffunction pregunta-general (?pregunta)
	(format t "¿%s?: " ?pregunta)
	(bind ?respuesta  (readline))
	?respuesta
)

;# HACE UNA PREGUNTA NUMERICA
(deffunction pregunta-numero (?pregunta)
	(format t "¿%s?: " ?pregunta)
	(bind ?respuesta (read))
	?respuesta
)

;# PREGUNTA NUMERICA DENTRO DE UN RANGO ENTERO
(deffunction pregunta-rango-entero (?pregunta ?rangini ?rangfinal)
	(format t "¿%s? [%d, %d]: " ?pregunta ?rangini ?rangfinal)
	(bind ?respuesta (read))
	(while (not(and(>= ?respuesta ?rangini)(<= ?respuesta ?rangfinal))) do
		(format t "%s [%d, %d]: " ?pregunta ?rangini ?rangfinal)
		(bind ?respuesta (read))
	)
	?respuesta
)

;# PREGUNTA NUMERICA DENTRO DE UN RANGO DECIMAL
(deffunction pregunta-rango-decimal (?pregunta ?rangini ?rangfinal)
	(format t "¿%s? [%.2f, %.2f]: " ?pregunta ?rangini ?rangfinal)
	(bind ?respuesta (read))
	(while (not(and(>= ?respuesta ?rangini)(<= ?respuesta ?rangfinal))) do
		(format t "%s [%.2f, %.2f]: " ?pregunta ?rangini ?rangfinal)
		(bind ?respuesta (read))
	)
	?respuesta
)

;# HACE UNA PREGUNTA ESPERANDO UNA LISTA DE VALORES
(deffunction pregunta-opciones (?pregunta $?valores-validos)
	;# SE RECORRE LA LISTA valores-validos y se convierten a minusculas
	(progn$
		(?var ?valores-validos)
		(lowcase ?var)
	)
	;# CON IMPLODE transformamos todos los valores del multislot 
	;# a string i nos permite imprimirlos directamente
	(format t "¿%s? (%s): " ?pregunta (implode$ ?valores-validos))
	(bind ?respuesta (lowcase (read)))
	;# Mientras que la respuesta no esté dentro de los valores validos
	;# se itera y se va repitiendo
	(while (not (member (lowcase ?respuesta) ?valores-validos)) do
		(format t "¿%s? (%s): " ?pregunta (implode$ ?valores-validos))
		(bind ?respuesta (lowcase (read)))
	)
	?respuesta
)

;# HACE UNA PREGUNTA ESPERANDO UNA LISTA DE VALORES
(deffunction pregunta-opciones-multiple (?pregunta $?valores-validos)
	(printout t "¿" ?pregunta "? " $?valores-validos ": ")
	(bind $?respuesta (str-explode (readline)))
	(bind ?repetir FALSE)
	(loop-for-count (?i 1 (length $?respuesta))
		(if (not (member$ (nth$ ?i $?respuesta) $?valores-validos)) then
			(bind ?repetir TRUE)
		)
	)
	(if ?repetir then
		(bind $?respuesta (pregunta-opciones-multiple ?pregunta $?valores-validos))
	)
	$?respuesta
)

(deffunction pregunta-opciones-multiple-dos (?pregunta $?valores-validos)
	(bind $?respuesta (pregunta-opciones-multiple ?pregunta $?valores-validos))
	(while (> (length$ $?respuesta) 2) do
		(printout t "El maximo son dos opciones." crlf)
		(bind $?respuesta (pregunta-opciones-multiple ?pregunta $?valores-validos))
	)
	$?respuesta
)

;# PREGUNTA BINARIA SI NO
(deffunction pregunta-si-o-no (?pregunta)
	(bind ?respuesta (pregunta-opciones ?pregunta si s no n))
	(if (or (eq (lowcase ?respuesta) si) (eq (lowcase ?respuesta) s))
		then TRUE
		else FALSE
	)
)


(deffunction preguntar-actividades ()
	;# actividades
	;# Obtener todas las posibles actividades
	(bind ?actividades (find-all-instances ((?class Actividad)) TRUE))
	;# Mostrar lista al usuario con formato "id: Nombre de la actividad"
	(printout t "Indique que actividades de las siguientes realiza de forma habitual:" crlf)
	;# Nos guardamos el id que tiene cada actividad, que es lo que escribira el usuario
	(bind $?lista-ids (create$ ))
	(loop-for-count (?i 1 (length ?actividades))
		(format t "%d: %s" ?i (send (nth$ ?i ?actividades) get-nombreActividad))
		(printout t crlf)
		(bind $?lista-ids (insert$ $?lista-ids ?i ?i))
	)
	;# Preguntar por la lista de ids
	(bind $?respuesta (pregunta-opciones-multiple "Que actividades realiza" ?lista-ids))
	(printout t crlf)
	;# Anadir los actividades que ha seleccionado a la lista de actividades-persona
	(bind $?actividades-persona (create$ ))
	(loop-for-count (?i 1 (length $?actividades))
		(if (member$ ?i $?respuesta) then
			(bind ?actividad (nth$ ?i ?actividades))
			(bind ?nombre-actividad (instance-name ?actividad))
			(bind $?actividades-persona (insert$ $?actividades-persona 1 ?nombre-actividad))
			(bind ?pregunta (format nil "Cuantas veces a la semana hace %s" ?nombre-actividad))
			(bind ?frecuencia (pregunta-rango-entero ?pregunta 1 7))
			(bind ?pregunta (format nil "Cuanto tiempo, en minutos"))
			(bind ?duracion (pregunta-rango-entero ?pregunta 1 600))
			(send ?actividad put-duracionTemporal (/ (* ?frecuencia ?duracion) 7)) ;# minutos al dia de ejericio
		)
	)
	(printout t crlf)
	$?actividades-persona
)

;###########################
;## OBTENCION DEL CLIENTE ##
;###########################

;# Preguntamos el nombre del cliente
;# Esto nos sirve para identificar personas que tenemos ya definidas
(defrule cargar-persona
	?preguntas <- (preguntas-iniciales ok)
	=>
	(retract ?preguntas)
	(bind ?nombre-introducido (pregunta-general "Como se llama"))
	;# Si hay una instancia con ese nombre, la usamos, sino habra que crearla
	(if (any-instancep ((?instancia Persona)) (eq ?instancia:nombrePersona ?nombre-introducido)) then
		(printout t "Cliente encontrado" crlf)
		;# TODO intentar reducir el coste de este trozo
		;# no tiene sentido que se haga todo esto, se ha de intentar reducir
		(bind ?persona (find-instance ((?instancia Persona)) (eq ?instancia:nombrePersona ?nombre-introducido))) 
		(bind ?persona (nth$ 1 ?persona))
		(bind $?actividades (preguntar-actividades))
		(send ?persona put-actividades $?actividades)
		(assert (instancia-persona ?persona))
		(assert (existe-persona ok))
	else
		(assert (nombre-persona ?nombre-introducido))
		(assert (existe-persona noOk))
	)	
)

;# Creacion de una persona nueva a partir de los datos entrados
(defrule crear-persona
	?existe <- (existe-persona noOk)
	(nombre-persona ?nombre)
	=>
	
	;# Sexo
	(bind ?respuesta (pregunta-opciones "Sexo" hombre h mujer m))
	(if (or (eq (lowcase ?respuesta) hombre) (eq (lowcase ?respuesta) h)) then
		(bind ?sexo macho)
	else
		(bind ?sexo hembra)
	)
	
	;# Edad
	(bind ?edad (pregunta-rango-entero "Edad" 1 120))
	
	;# Peso
	(bind ?peso (pregunta-rango-entero "Peso en kg" 1 500))
	
	;# Altura
	(bind ?altura (pregunta-rango-decimal "Altura en m" 1.0 3.0))
	(printout t crlf)
	
	;# Problemas de salud
	;# Obtener todos los posibles problemas
	(bind ?problemas (find-all-instances ((?class ProblemaSalud)) TRUE))
	;# Mostrar lista al usuario con formato "id: Nombre del problema"
	(printout t "Indique si tiene alguno de los siguientes problemas de salud:" crlf)
	;# Nos guardamos el id que tiene cada problema, que es lo que escribira el usuario
	(bind $?lista-ids (create$ ))
	(loop-for-count (?i 1 (length ?problemas))
		(format t "%d: %s" ?i (send (nth$ ?i ?problemas) get-nombreProblema))
		(printout t crlf)
		(bind $?lista-ids (insert$ $?lista-ids ?i ?i))
	)
	;# Preguntar por la lista de ids
	(bind $?respuesta (pregunta-opciones-multiple "Que problemas padece" ?lista-ids))
	;# Anadir los problemas que ha seleccionado a la lista de problemas-persona
	(bind ?problemas-persona (create$ ))
	(loop-for-count (?i 1 (length $?problemas))
		(if (member$ ?i $?respuesta) then
			(bind ?problema (instance-name (nth$ ?i ?problemas)))
			(bind $?problemas-persona (insert$ $?problemas-persona 1 ?problema))
		)
	)
	(printout t crlf)

	(bind $?actividades-persona (preguntar-actividades))

	;# Sabe nadar
	(bind ?respuesta (pregunta-si-o-no "Sabe nadar"))
	(if ?respuesta then
		(bind ?sabe-nadar si)
	else
		(bind ?sabe-nadar no)
	)
	(printout t crlf)

	;# Objetivos
	;# Obtener todos los posibles objetivos
	(bind ?objetivos (find-all-instances ((?class Objetivo)) TRUE))
	;# Mostrar lista al usuario con formato "id: Nombre del objetivo"
	(printout t "Indique que espera mejorar haciendo ejericio en el gimnasio:" crlf)
	;# Nos guardamos el id que tiene cada objetivo, que es lo que escribira el usuario
	(bind $?lista-ids (create$ ))
	(loop-for-count (?i 1 (length ?objetivos))
		(format t "%d: %s" ?i (send (nth$ ?i ?objetivos) get-nombreObjetivo))
		(printout t crlf)
		(bind $?lista-ids (insert$ $?lista-ids ?i ?i))
	)
	;# Preguntar por la lista de ids
	(bind $?respuesta (pregunta-opciones-multiple-dos "Que le gustaria mejorar (seleccione un par)" ?lista-ids))
	;# Anadir los objetivos que ha seleccionado a la lista de objetivos-persona
	(bind ?objetivos-persona (create$ ))
	(loop-for-count (?i 1 (length$ $?objetivos))
		(if (member$ ?i $?respuesta) then
			(bind ?objetivo (instance-name (nth$ ?i ?objetivos)))
			(bind $?objetivos-persona (insert$ $?objetivos-persona 1 ?objetivo))
		)
	)
	(printout t crlf)

	;# Tiempo disponible
	(bind $?dias-semana (create$ Lunes Martes Miercoles Jueves Viernes Sabado Domingo))
	(bind $?tiempo-disponible (create$ ))
	(loop-for-count (?i 1 (length$ $?dias-semana))
		(bind ?dia (str-cat (nth$ ?i $?dias-semana)))
		(bind ?pregunta (format nil "Cuanto tiempo libre tiene el %s, en horas" ?dia))
		(bind ?respuesta (pregunta-rango-decimal ?pregunta 0 6))
		;# Cada hora tiene 6 unidades de tiempo
		(bind ?tiempo (integer (* ?respuesta 6)))
		(bind $?tiempo-disponible (insert$ $?tiempo-disponible ?i ?tiempo))
	)
	
	;# Crear nueva instancia de persona y hacer assert
	(assert (instancia-persona (make-instance nueva-persona of Persona
		(sexo ?sexo)
		(altura ?altura)
		(edad ?edad)
		(nombrePersona ?nombre)
		(peso ?peso)
		(sabeNadar ?sabe-nadar)
		(tiempoDisponible ?tiempo-disponible)
		(problemas $?problemas-persona)
		(objetivos $?objetivos-persona)
		(actividades $?actividades-persona)
	)))
	
	(printout t crlf "Nuevo cliente " ?nombre " creado." crlf)
	(pregunta-opciones-multiple "presione enter para continuar" (create$ ))

	(retract ?existe)
	(assert (existe-persona ok))
)

(deftemplate tipos-ejercicios
	(slot filtrado)
	(multislot lista)
)

(defrule cargar-tipos-ejercicios
	(not (tipos-ejercicios))
	=>
	(assert (tipos-ejercicios (filtrado no) (lista (create$ EjercicioPiscina EjercicioPolideportivo EjercicioFitness EjercicioGuiado))))
)

;#####################
;## Datos derivados ##
;#####################
;# Datos concretos que se obtienen a partir de los datos introducidos

;# Con los datos cargados, calcular el gasto energetico diario
(defrule gasto-energetico-diario
	(existe-persona ok)
	(instancia-persona ?persona)
	=>
	(send ?persona calcularGastoEnergeticoDiario)
)

;## CAMBIO DE MODULO ##########################################################
(defrule end-obtencion
	(declare (salience -10000))
	=>
	(printout t "[END OBTENCION]" crlf crlf "[ABSTRACCION]" crlf)
	(focus modulo-abstraccion)
)
